//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "ddk.h"

#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#include <tfssys.h>
#include <misc.h>
#include <fat.h>

/*
 * fat12_loadfat()
 *    Load FAT handling
 *
 * Read in the FAT, convert if needed.
 */
int fat12::
fat12_loadfat(class fatfs_sharedata *p_Sharedata)
{
    if (fat == 0 || dirtymap == 0) {
        DPRINTF(("fat12_init error\n"));
        return -1;
    }
    /*
     * clear FAT dirtymap
     */
    bzero(dirtymap, dirtymapsize);
    /*
     * Seek to FAT table on disk, read into buffer
     */
    ECode ec = ReadDevice(
                p_Sharedata->pBlkDev,
                (p_Sharedata->bootb.BPB_ResvdSecCnt +
                    p_Sharedata->bpb_base) * p_Sharedata->secsz,
                fatlen,
                (PByte)fat,
                NULL, NULL);
    if (FAILED(ec)) {
        DPRINTF(("ERROR: Failed to Read from the offset:%d", \
                            p_Sharedata->bootb.BPB_ResvdSecCnt * p_Sharedata->secsz));
        return -1;
    }

    return 0;
}

/*
 * fat_init()
 *    Initialize FAT handling
 */
int fat12::
fat12_init(class fatfs_sharedata *pSharedata)
{
    ASSERT_DEBUG(pSharedata!=NULL, "fat_setlen: bad clust");
    p_Sharedata = pSharedata;
    p_Sharedata->AddRef();

    /*
     * Get map of dirty sectors in FAT.  "nclust" is multipled
     * by 1.5, by scaling the values by 2 (1.5 bytes * 2 == 3).
     */
    dirtymapsize = roundup((p_Sharedata->nclust * 3)/2, p_Sharedata->secsz) / p_Sharedata->secsz;
    dirtymap = (UInt8 *)malloc(dirtymapsize);
    ASSERT_DEBUG(dirtymap, "fat12_init: dirtymap");

    /*
     * Get memory for FAT table
     */
    fatlen = p_Sharedata->bootb.BPB_FATSz16 * p_Sharedata->secsz;
    fat = (UInt8 *)malloc(fatlen);

    return fat12_loadfat(p_Sharedata);
}

/*
 * dirty()
 *    Flag a given entry as being dirtied
 *
 * Need to do squirrely math for the 1.5-byte entries
 */
void fat12::
dirty(UInt32 idx)
{
    UInt32 offset;

    /*
     * Scale the entry size up by 2 (1.5 bytes * 2 == 3) so we can
     * calculate in integer; scale divisor by 2, also.
     */
    offset = (idx * 3) / (p_Sharedata->secsz * 2);

    /*
     * Flag the containing sector as dirty
     */
    dirtymap[offset] = 1;

    /*
     * Flag the next slot up, too.  Each entry spans two bytes, and
     * these two bytes could span sectors.
     */
    offset = (++idx * 3) / (p_Sharedata->secsz * 2);
    dirtymap[offset] = 1;
}

/*
 * set()
 *    Set a 12-bit slot to the given value
 *
 * Automatically marks this part of the FAT dirty, too.
 */
void fat12::
set(UInt32 idx, fat12_t val)
{
    UInt8 *pos;

    pos = &fat[(idx * 3) / 2];
    if (idx & 1) {
        *pos = (*pos & 0x0F) | ((val & 0x0F) << 4);
        *++pos = (val >> 4);
    }
    else {
        *pos++ = (UInt8)val;
        *pos = (*pos & 0xF0) | ((val & 0xF00) >> 8);
    }
    dirty(idx);
}

/*
 * get()
 *    Get a 12-bit slot value
 */
fat12_t fat12::
get(UInt32 idx)
{
    UInt8 *pos;

    pos = &fat[(idx * 3) / 2];
    if (idx & 1) {
        return ((pos[0] & 0xF0) >> 4) | (pos[1] << 4);
    }
    else {
        return pos[0] | ((pos[1] & 0x0F) << 8);
    }
}

/*
 * fat12_setlen()
 *    Twiddle FAT allocation to match the indicated length
 *
 * Returns 0 if it could be done; 1 if it failed.
 */
int fat12::
fat12_setlen(struct clust *c, UInt32 newclust)
{
    UInt32 clust_cnt, x;
    UInt32 cl, *ctmp;

    /*
     * Getting smaller--free stuff at the end
     */
    if (c->c_nclust > newclust) {
        /*
         * it is very surprise to find this expression, nxt_clust may be
         * invalid
         */
        //nxt_clust = newclust;  ????
        nxt_clust = c->c_clust[newclust];
        for (x = newclust; x < c->c_nclust;++x) {
            cl = c->c_clust[x];
#ifdef DEBUG
            if ((cl > p_Sharedata->nclust + 1) || (cl < 2)) {
                UInt32 z;

                DPRINTF(("bad cluster 0x%x\n", cl));
                DPRINTF(("clusters in file:"));
                for (z = 0; z < c->c_nclust;++z) {
                    DPRINTF((" %x", c->c_clust[z]));
                }
                ASSERT_DEBUG(0, "fat_setlen: bad clust");
            }
#endif
            set(cl, 0);
            c->c_clust[x] = 0;
            DeleteBlock(p_Sharedata->pBlkDev, BOFF(cl),
                            p_Sharedata->bootb.BPB_SecPerClus);
        }
        if (newclust > 0) {
            set(cl = c->c_clust[newclust-1], FAT12_EOF);
        }
        if (newclust < c->n_newclust)
            c->n_newclust = 0x7fffffff;    // new added
        c->c_nclust = newclust;
        p_Sharedata->fat_dirty = 1;
        return(0);
    }

    /*
     * Trying to grow.  See if we can get the blocks.  If we can't,
     * we bail out.  The allocation is done in two passes, so that
     * when we bail after running out of space there's nothing which
     * needs to be undone.  The realloc()'ed c_clust is harmless.
     */
    //ctmp = (UInt32 *)realloc(c->c_clust, newclust * sizeof(UInt32));
    ctmp = (UInt32 *)malloc(newclust * sizeof(UInt32));
    if (ctmp == 0) {
        return(1);
    }
    memcpy(ctmp, c->c_clust, c->c_nclust * sizeof(UInt32));
    if (c->c_clust) {
        free(c->c_clust);
    }
    c->c_clust = ctmp;
    cl = nxt_clust;
    clust_cnt = 0;
    for (x = c->c_nclust; x < newclust;++x) {
        /*
         * Scan for next free cluster
         */
        while ((clust_cnt++< p_Sharedata->nclust) && get(cl)) {
            if (++cl > p_Sharedata->nclust + 1) {
                cl = 2;
            }
        }

        /*
         * If we didn't find one, fail
         */
        if (clust_cnt > p_Sharedata->nclust) {
            return(1);
        }

        /*
         * Tag where last search finished to save time
         */
        nxt_clust = cl + 1;
        if (nxt_clust > p_Sharedata->nclust + 1) {
            nxt_clust = 2;
        }

        /*
         * Sanity
         */
        ASSERT_DEBUG((cl >= 2) && (cl < p_Sharedata->nclust + 2),
            "clust_setlen: bad FAT");

        /*
         * Otherwise add it to our array.  We will flag it
         * consumed soon.
         */
        ctmp[x] = cl++;

        /*
         * clust 0, 1 are both reserved, so jump to clust 2 will be more safe
         */
        if (cl > p_Sharedata->nclust + 1) {
            cl = 2;
        }
    }

    /*
     * When we get here, the new clusters for the file extension
     * have been found and filled into the c_clust[] array.
     * We now go back and (1) flag the FAT entries consumed,
     * which also builds the cluster chain, and (2) update the
     * count of clusters for this file.
     */

    /*
     * Chain last block which already existed onto this new
     * space.
     */
    if (c->c_nclust > 0) {
        set(c->c_clust[c->c_nclust-1],(fat12_t) c->c_clust[c->c_nclust]);
    }

    /*
     * Chain all the new clusters together
     */
    for (x = c->c_nclust; x < newclust-1;++x) {
        set(c->c_clust[x], (fat12_t)c->c_clust[x+1]);
    }

    /*
     * Mark the EOF cluster for the last one
     */
    set(c->c_clust[newclust-1], FAT12_EOF);
    if (c->n_newclust > newclust)
        c->n_newclust = c->c_nclust-1;    // new added
    c->c_nclust = newclust;
    p_Sharedata->fat_dirty = 1;
    return(0);
}

/*
 * fat12_alloc()
 *    Allocate a description of the given cluster chain
 */
struct clust * fat12::
fat12_alloc(struct clust *c, struct directory *d)
{
    UInt32 nclust = 1, x, start;

    /*
     * Get starting cluster from directory entry
     */
    start = d->DIR_Start;

    /*
     * Scan the chain to get its length
     */
    for (x = start; ((get(x) < FAT12_RESERVED) && (get(x) >= 2)); x = get(x)) {
        ASSERT_DEBUG(x >= 2, "alloc_clust: free cluster in file");
        nclust++;
    }

    /*
     * Allocate the description array
     */
    c->c_clust = (UInt32 *)malloc(nclust * sizeof(UInt32));
    if (c->c_clust == 0) {
        return(0);
    }
    c->c_nclust = nclust;

    /*
     * Scan again, recording each cluster number
     */
    x = start;
    nclust = 0;
    do {
        c->c_clust[nclust++] = x;
        x = get(x);
    } while ((x < FAT12_RESERVED) && (x >= 2));
    return(c);
}

/*
 * fat12_sync()
 *    Write a FAT12 using the dirtymap to minimize I/O
 */
void fat12::
fat12_sync(void)
{
    int x, cnt, pass, fatsize;
    off_t off;

    fatsize = (fatlen+p_Sharedata->secsz-1) >> p_Sharedata->secshift;
    /*
     * There are two copies of the FAT, so do them iteratively
     */
    for (pass = 0; pass <= 1;++pass) {
        /*
         * Calculate the offset once per pass
         */
        off = pass*(long)fatlen;

        /*
         * Walk across the dirty map, find the next dirty sector
         * of FAT information to write out.
         */
        for (x = 0; x < (int)dirtymapsize;) {
            /*
             * Not dirty.  Advance to next sector's worth.
             */
            if (!dirtymap[x]) {
                x += 1;
                continue;
            }

            /*
             * Now find runs, so we can flush adjacent sectors
             * in a single operation.
             */
            for (cnt = 1; ((x + cnt) < (int)dirtymapsize) &&
                dirtymap[x + cnt];++cnt) {
                ;
            }

            /*
             * Seek to the right place, and write the data
             */
            if (x < fatsize) {
                ECode ec = WriteDevice(
                    p_Sharedata->pBlkDev,
                    ((p_Sharedata->bootb.BPB_ResvdSecCnt +
                        x+p_Sharedata->bpb_base) << p_Sharedata->secshift) + off,
                    (cnt << p_Sharedata->secshift), (PByte) & fat[x << p_Sharedata->secshift], NULL,
                    NULL);
                if (FAILED(ec)) {
                    DPRINTF(("ERROR: Failed to Write from the offset:%d", \
                            ((p_Sharedata->bootb.BPB_ResvdSecCnt + x) << \
                                p_Sharedata->secshift) + off));
                    return;
                }
            }
            else {
                DPRINTF((
                    "WARNING: Overflow in writting FAT12 table. offset: %d, \
                    len: %d\n", x, cnt));
            }
            /*
             * Advance x
             */
            x += cnt;
        }
    }

    /*
     * Clear dirtymap--everything's been flushed successfully
     */
    bzero(dirtymap, dirtymapsize);
}

fat12::fat12()
{
    fat = NULL;
    dirtymap = NULL;
    dirtymapsize = 0;
    nxt_clust = 2;
    fatlen = 0;
    p_Sharedata = NULL;
}

fat12::~fat12()
{
    if (fat != NULL) {
        free(fat);
    }

    if (dirtymap != NULL) {
        free(dirtymap);
    }
    if (p_Sharedata)
        p_Sharedata->Release();
}

/*
 * Calculate the free space in disk
 */
UInt64 fat12::fat12_calcfreespace()
{
    UInt32 totalFreeClust = 0, clustEnd;
    UInt64 totalByte = 0;

    clustEnd = p_Sharedata->nclust + 1;

    for (UInt32 x = 2; x <= clustEnd;++x) {
        if (!get(x)) {
            totalFreeClust++;
        }
    }

    totalByte = ((UInt64)totalFreeClust) * p_Sharedata->clsize;
    return totalByte;
}

#if defined(_DEBUG)
void fat12::fat12_stat(UInt32 *Fat_secs)
{
    UInt32 i, cnt = 0;
    for (i = 0; i < dirtymapsize;++i) {
        if (dirtymap[i])
            ++cnt;
    }
    *Fat_secs = cnt;
}
#endif
